/* Copyright  2009
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/**
 * @filename: esql.c
 */
#include <config.h>
#include <include/global.h>
#include <include/common.h>

#include "libclient_priv.h"

#include "libclient.h"
#include "pack.h"
#include "clienterrmsg.h"
#include "esql.h"
#include "net_serv.h"
#include "esql_data.h"

#include <include/gettext.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <netdb.h>
#include <assert.h>
#include <pwd.h>
#include <sys/socket.h>
#include <signal.h>
#include <errno.h>


//#define CONNECT_TIMEOUT 0

static bool  esql_client_init= false;
unsigned int esql_server_last_errno;

/* Server error code and message */
char esql_server_last_error[LIBCLIENT_ERRMSG_SIZE];

/*
  Note that the esql argument must be initialized with esql_init()
  before calling client_connect !
*/



static ESQL_METHODS client_methods=
{
  client_cli_read_query_result,                       /* read_query_result */
  client_cli_advanced_command,                        /* advanced_command */
  client_cli_read_rows,                               /* read_rows */
  client_cli_use_result,                              /* use_result */
  client_cli_fetch_lengths,                           /* fetch_lengths */
  client_cli_flush_use_result,                        /* flush_use_result */
  client_cli_list_fields,                             /* list_fields */
  client_cli_unbuffered_fetch,                        /* unbuffered_fetch */
  client_cli_read_statistics,                         /* read_statistics */
  client_cli_read_query_result,                       /* next_result */
};



/****************************************************************************
  Init ESQL structure or allocate one
****************************************************************************/

ESQL *
client_create(ESQL *ptr)
{

  if (!esql_client_init)
  {
    esql_client_init=true;

    if (!client_get_default_port())
    {
      client_set_default_port(COLLAPSAR_PORT);
      {

        char *env;

        /*
          if builder specifically requested a default port, use that
          (even if it coincides with our factory default).
          only if they didn't do we check /etc/services (and, failing
          on that, fall back to the factory default of 4427).
          either default can be overridden by the environment variable
          COLLAPSAR_TCP_PORT, which in turn can be overridden with command
          line options.
        */

#ifdef COLLAPSAR_PORT_DEFAULT
        struct servent *serv_ptr;
        if ((serv_ptr = getservbyname("collapsar", "tcp")))
          client_set_default_port((uint32_t) ntohs((uint16_t) serv_ptr->s_port));
#endif
        if ((env = getenv("COLLAPSAR_TCP_PORT")))
          client_set_default_port((uint32_t) atoi(env));
      }
    }
#if defined(SIGPIPE)
    (void) signal(SIGPIPE, SIG_IGN);
#endif
  }

  if (ptr == NULL)
  {
    ptr= (ESQL *) malloc(sizeof(ESQL));

    if (ptr == NULL)
    {
      client_set_error(NULL, CR_OUT_OF_MEMORY, client_sqlstate_get_unknown());
      return 0;
    }
    bzero(ptr,sizeof(ESQL));
    ptr->free_me=1;
  }
  else
  {
    bzero(ptr,sizeof(ESQL));
  }

  ptr->options.connect_timeout= CONNECT_TIMEOUT;
  strcpy(ptr->net.sqlstate,  client_sqlstate_get_not_error());

  /*
    Only enable LOAD DATA INFILE by default if configured with
    --enable-local-infile
  */

#if defined(ENABLED_LOCAL_INFILE)
  ptr->options.client_flag|= CLIENT_LOCAL_FILES;
#endif

  ptr->options.methods_to_use= OPT_GUESS_CONNECTION;
  ptr->options.report_data_truncation= true;  /* default */

  /*
    By default we don't reconnect because it could silently corrupt data (after
    reconnection you potentially lose table locks, user variables, session
    variables (transactions but they are specifically dealt with in
    client_reconnect()).
    This is a change: < 5.0.3 esql->reconnect was set to 1 by default.
    How this change impacts existing apps:
    - existing apps which relyed on the default will see a behavior change;
    they will have to set reconnect=1 after client_connect().
    - existing apps which explicitly asked for reconnection (the only way they
    could do it was by setting esql.reconnect to 1 after client_connect())
    will not see a behavior change.
    - existing apps which explicitly asked for no reconnection
    (esql.reconnect=0) will not see a behavior change.
  */
  ptr->reconnect= 0;

  return ptr;
}


static void read_user_name(char *name)
{
  if (geteuid() == 0)
    strcpy(name,"root");    /* allow use of surun */
  else
  {
#ifdef HAVE_GETPWUID
    struct passwd *skr;
    const char *str;
    if ((str=getlogin()) == NULL)
    {
      if ((skr=getpwuid(geteuid())) != NULL)
  str=skr->pw_name;
      else if (!(str=getenv("USER")) && !(str=getenv("LOGNAME")) &&
         !(str=getenv("LOGIN")))
  str="UNKNOWN_USER";
    }
    strncpy(name,str,USERNAME_LENGTH);
#elif HAVE_CUSERID
    (void) cuserid(name);
#else
    strcpy(name,"UNKNOWN_USER");
#endif
  }
  return;
}

ESQL *
 client_connect(ESQL *esql,const char *host, const char *user,
                const char *passwd, const char *db,
                uint32_t port,
                const char * unix_port,
                uint32_t client_flag)
{
  (void)unix_port;
  char          buff[NAME_LEN+USERNAME_LENGTH+100];
  char          *end,*host_info=NULL;
  uint32_t      pkt_length;
  NET           *net= &esql->net;

  esql->methods= &client_methods;
  net->vio = 0;        /* If something goes wrong */
  esql->client_flag=0;      /* For handshake */

  /* Some empty-string-tests are done because of ODBC */
  if (!host || !host[0])
    host=esql->options.host;
  if (!user || !user[0])
  {
    user=esql->options.user;
    if (!user)
      user= "";
  }
  if (!passwd)
  {
    passwd=esql->options.password;
    if (!passwd)
      passwd= "";
  }
  if (!db || !db[0])
    db=esql->options.db;
  if (!port)
    port=esql->options.port;

  esql->server_status=SERVER_STATUS_AUTOCOMMIT;

  /*
    Part 0: Grab a socket and connect it to the server
  */
  if (!net->vio)
  {
    struct addrinfo *res_lst, hints, *t_res;
    int gai_errno;
    char port_buf[NI_MAXSERV];

    if (!port)
      port=  client_get_default_port();

    if (!host)
      host= LOCAL_HOST;

    snprintf(host_info=buff, sizeof(buff)-1, CLIENT_ER(CR_TCP_CONNECTION), host);

    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype= SOCK_STREAM;

    snprintf(port_buf, NI_MAXSERV, "%d", port);
    gai_errno= getaddrinfo(host, port_buf, &hints, &res_lst);

    if (gai_errno != 0)
    {
      client_set_extended_error(esql, CR_UNKNOWN_HOST, client_sqlstate_get_unknown(),
                                  CLIENT_ER(CR_UNKNOWN_HOST), host, errno);

      goto error;
    }

    for (t_res= res_lst; t_res != NULL; t_res= t_res->ai_next)
    {
      int sock= socket(t_res->ai_family, t_res->ai_socktype,
                       t_res->ai_protocol);
      if (sock < 0)
        continue;

      net->vio= client_vio_new(sock, VIO_TYPE_TCPIP, VIO_BUFFERED_READ);
      if (! net->vio )
      {
        close(sock);
        continue;
      }

      if ( client_connect_with_timeout(sock, t_res->ai_addr, t_res->ai_addrlen,
                                       esql->options.connect_timeout))
      {
        client_vio_delete(net->vio);
        net->vio= 0;
        continue;
      }
      break;
    }

    freeaddrinfo(res_lst);
  }

  if (!net->vio)
  {
    client_set_extended_error(esql, CR_CONN_HOST_ERROR, client_sqlstate_get_unknown(),
                             CLIENT_ER(CR_CONN_HOST_ERROR), host, errno);
    goto error;
  }

  if ( client_net_init(net, net->vio))
  {
    client_vio_delete(net->vio);
    net->vio = 0;
    client_set_error(esql, CR_OUT_OF_MEMORY, client_sqlstate_get_unknown());
    goto error;
  }
  client_vio_keepalive(net->vio,true);

  /* If user set read_timeout, let it override the default */
  if (esql->options.read_timeout)
     client_net_set_read_timeout(net, esql->options.read_timeout);

  /* If user set write_timeout, let it override the default */
  if (esql->options.write_timeout)
     client_net_set_write_timeout(net, esql->options.write_timeout);

  if (esql->options.max_allowed_packet)
    net->max_packet_size= esql->options.max_allowed_packet;

  /* Get version info */
  esql->protocol_version= PROTOCOL_VERSION;  /* Assume this */
  if (esql->options.connect_timeout &&
       client_vio_poll_read(net->vio, esql->options.connect_timeout))
  {
    client_set_extended_error(esql, CR_SERVER_LOST, client_sqlstate_get_unknown(),
                              CLIENT_ER(CR_SERVER_LOST_INITIAL_COMM_WAIT),
                               errno);
    goto error;
  }

  /*
    Part 1: Connection established, read and parse first packet
  */

  if ((pkt_length=client_cli_safe_read(esql)) == packet_error)
  {
    if (esql->net.last_errno == CR_SERVER_LOST)
        client_set_extended_error(esql, CR_SERVER_LOST,  client_sqlstate_get_unknown(),
                                   CLIENT_ER(CR_SERVER_LOST_INITIAL_COMM_READ),
                                 errno);
    goto error;
  }
  /* Check if version of protocol matches current one */

  esql->protocol_version= net->read_pos[0];
  if (esql->protocol_version != PROTOCOL_VERSION)
  {
    client_set_extended_error(esql, CR_VERSION_ERROR,  client_sqlstate_get_unknown(),
                              CLIENT_ER(CR_VERSION_ERROR), esql->protocol_version,
                               PROTOCOL_VERSION);
    goto error;
  }
  end= strchr((char*) net->read_pos+1, '\0');
  esql->thread_id=uint4korr(end+1);
  end+=5;
  /*
    Scramble is split into two parts because old clients does not understand
    long scrambles; here goes the first part.
  */
  strncpy(esql->scramble, end, SCRAMBLE_LENGTH_323);
  end+= SCRAMBLE_LENGTH_323+1;

  if (pkt_length >= (uint32_t) (end+1 - (char*) net->read_pos))
    esql->server_capabilities=uint2korr(end);
  if (pkt_length >= (uint32_t) (end+18 - (char*) net->read_pos))
  {
    /* New protocol with 16 bytes to describe server characteristics */
    esql->server_language=end[2];
    esql->server_status=uint2korr(end+3);
  }
  end+= 18;
  if (pkt_length >= (uint32_t) (end + SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323 + 1 -
                            (char *) net->read_pos))
    strncpy(esql->scramble+SCRAMBLE_LENGTH_323, end,
            SCRAMBLE_LENGTH-SCRAMBLE_LENGTH_323);
  else
    esql->server_capabilities&= ~CLIENT_SECURE_CONNECTION;

  if (esql->options.secure_auth && passwd[0] &&
      !(esql->server_capabilities & CLIENT_SECURE_CONNECTION))
  {
    client_set_error(esql, CR_SECURE_AUTH,  client_sqlstate_get_unknown());
    goto error;
  }

  /* Save connection information */
  if (!(esql->host_info= (char *)malloc(strlen(host_info)+1+strlen(host)+1
                                           +(end - (char*) net->read_pos))) ||
      !(esql->user=strdup(user)) ||
      !(esql->passwd=strdup(passwd)))
  {
    client_set_error(esql, CR_OUT_OF_MEMORY, client_sqlstate_get_unknown());
    goto error;
  }
  esql->host= esql->host_info+strlen(host_info)+1;
  esql->server_version= esql->host+strlen(host)+1;
  strcpy(esql->host_info,host_info);
  strcpy(esql->host,host);
  strcpy(esql->server_version,(char*) net->read_pos+1);
  esql->port=port;

  /*
    Part 2: format and send client info to the server for access check
  */

  client_flag|=esql->options.client_flag;
  client_flag|=CLIENT_CAPABILITIES;
  if (client_flag & CLIENT_MULTI_STATEMENTS)
    client_flag|= CLIENT_MULTI_RESULTS;

  if (db)
    client_flag|=CLIENT_CONNECT_WITH_DB;

  /* Remove options that server doesn't support */
  client_flag= ((client_flag &
                 ~(CLIENT_COMPRESS | CLIENT_SSL)) |
                (client_flag & esql->server_capabilities));
  client_flag&= ~CLIENT_COMPRESS;

  int4store(buff, client_flag);
  int4store(buff+4, net->max_packet_size);
  buff[8]= (char) 45; // utf8 charset number
  memset(buff+9, 0, 32-9);
  end= buff+32;

  esql->client_flag=client_flag;

  /* This needs to be changed as it's not useful with big packets */
  if (user && user[0])
    strncpy(end,user,USERNAME_LENGTH);          /* Max user name */
  else
    read_user_name((char*) end);

  /* We have to handle different version of handshake here */
  end= strchr(end, '\0') + 1;
  if (passwd[0])
  {
    {
      *end++= SCRAMBLE_LENGTH;
      memset(end, 0, SCRAMBLE_LENGTH-1);
      memcpy(end, passwd, strlen(passwd));
      end+= SCRAMBLE_LENGTH;
    }
  }
  else
    *end++= '\0';                               /* empty password */

  /* Add database if needed */
  if (db && (esql->server_capabilities & CLIENT_CONNECT_WITH_DB))
  {
    size_t db_len= strlen(db);

    if (db_len >= NAME_LEN)
      db_len= NAME_LEN - 1;
    end= memcpy(end, db, db_len);
    end[db_len]= 0;
    end+= (db_len + 1);

    esql->db= strdup(db);
    db= 0;
  }
  /* Write authentication package */
  if ( client_net_write(net, (unsigned char*) buff, (size_t) (end-buff)) || client_net_flush(net))
  {
     client_set_extended_error(esql, CR_SERVER_LOST,  client_sqlstate_get_unknown(),
                               CLIENT_ER(CR_SERVER_LOST_SEND_AUTH),
                               errno);
    goto error;
  }

  /*
    Part 3: Authorization data's been sent. Now server can reply with
    OK-packet, or re-request scrambled password.
  */

  if ((pkt_length=client_cli_safe_read(esql)) == packet_error)
  {
    if (esql->net.last_errno == CR_SERVER_LOST)
       client_set_extended_error(esql, CR_SERVER_LOST, client_sqlstate_get_unknown(),
                                 CLIENT_ER(CR_SERVER_LOST_READ_AUTH),
                                 errno);
    goto error;
  }

  if (client_flag & CLIENT_COMPRESS)    /* We will use compression */
    net->compress=1;


  if (db &&  client_select_db(esql, db))
  {
    if (esql->net.last_errno == CR_SERVER_LOST)
       client_set_extended_error(esql, CR_SERVER_LOST, client_sqlstate_get_unknown(),
                                 CLIENT_ER(CR_SERVER_LOST_SETTING_DB),
                                 errno);
    goto error;
  }


  return(esql);

error:
  {
    /* Free alloced memory */
    client_disconnect(esql);
    client_close_free(esql);
    if (!(((uint32_t) client_flag) & CLIENT_REMEMBER_OPTIONS))
       client_close_free_options(esql);
  }
  return(0);
}




/**************************************************************************
  Set current database
**************************************************************************/

int
client_select_db(ESQL *esql, const char *db)
{
  int error;

  if ((error=simple_command(esql,COM_INIT_DB, (const unsigned char*) db,
                            (uint32_t) strlen(db),0)))
    return(error);
  if (esql->db != NULL)
    free(esql->db);
  esql->db=strdup(db);
  return(0);
}

bool client_reconnect(ESQL *esql)
{
  ESQL tmp_esql;
  assert(esql);

  if (!esql->reconnect ||
      (esql->server_status & SERVER_STATUS_IN_TRANS) || !esql->host_info)
  {
    /* Allow reconnect next time */
    esql->server_status&= ~SERVER_STATUS_IN_TRANS;
    client_set_error(esql, CR_SERVER_GONE_ERROR,client_sqlstate_get_unknown());
    return(1);
  }
  client_create(&tmp_esql);
  tmp_esql.options= esql->options;
  tmp_esql.options.cnf_file= tmp_esql.options.cnf_group= 0;

  if (! client_connect(&tmp_esql,esql->host,esql->user,esql->passwd,
                       esql->db, esql->port, 0,
                       esql->client_flag | CLIENT_REMEMBER_OPTIONS))
  {
    esql->net.last_errno= tmp_esql.net.last_errno;
    strcpy(esql->net.last_error, tmp_esql.net.last_error);
    strcpy(esql->net.sqlstate, tmp_esql.net.sqlstate);
    return(1);
  }

  tmp_esql.reconnect= 1;
  tmp_esql.free_me= esql->free_me;

  /* Don't free options as these are now used in tmp_esql */
  memset(&esql->options, 0, sizeof(esql->options));
  esql->free_me=0;
  client_close(esql);
  *esql=tmp_esql;
  client_net_clear(&esql->net, 1);
  esql->affected_rows= ~(uint64_t) 0;
  return(0);
}

/**************************************************************************
  Shut down connection
**************************************************************************/

void client_disconnect(ESQL *esql)
{
  int save_errno= errno;
  if (esql->net.vio != 0)
  {
    client_vio_delete(esql->net.vio);
    esql->net.vio= 0;          /* Marker */
  }
  client_net_end(&esql->net);
  client_free_old_query(esql);
  errno= save_errno;
}


/*************************************************************************
  Send a QUIT to the server and close the connection
  If handle is alloced by ESQL connect free it.
*************************************************************************/

void client_close_free_options(ESQL *esql)
{
  if (esql->options.user != NULL)
    free(esql->options.user);
  if (esql->options.host != NULL)
    free(esql->options.host);
  if (esql->options.password != NULL)
    free(esql->options.password);
  if (esql->options.db != NULL)
    free(esql->options.db);
  if (esql->options.cnf_file != NULL)
    free(esql->options.cnf_file);
  if (esql->options.cnf_group != NULL)
    free(esql->options.cnf_group);
  if (esql->options.client_ip != NULL)
    free(esql->options.client_ip);
  memset(&esql->options, 0, sizeof(esql->options));
  return;
}


void client_close_free(ESQL *esql)
{
  if (esql->host_info != NULL)
    free((unsigned char*) esql->host_info);
  if (esql->user != NULL)
    free(esql->user);
  if (esql->passwd != NULL)
    free(esql->passwd);
  if (esql->db != NULL)
    free(esql->db);
  if (esql->info_buffer != NULL)
    free(esql->info_buffer);
  esql->info_buffer= 0;

  /* Clear pointers for better safety */
  esql->host_info= esql->user= esql->passwd= esql->db= 0;
}


void client_close(ESQL *esql)
{
  if (esql)          /* Some simple safety */
  {
    /* If connection is still up, send a QUIT message */
    if (esql->net.vio != 0)
    {
      client_free_old_query(esql);
      esql->status=ESQL_STATUS_READY; /* Force command */
      esql->reconnect=0;
      simple_command(esql,COM_QUIT,(unsigned char*) 0,0,1);
      client_disconnect(esql);      /* Sets esql->net.vio= 0 */
    }
    client_close_free_options(esql);
    client_close_free(esql);
    if (esql->free_me)
      free((unsigned char*) esql);
  }
  return;
}


bool client_cli_read_query_result(ESQL *esql)
{
  unsigned char *pos;
  uint32_t field_count;
  ESQL_DATA *fields;
  uint32_t length;

  if ((length = client_cli_safe_read(esql)) == packet_error)
    return(1);
  client_free_old_query(esql);    /* Free old result */

  pos=(unsigned char*) esql->net.read_pos;
  if ((field_count=  client_net_field_length(&pos)) == 0)
  {
    esql->affected_rows=   client_client_net_field_length_ll(&pos);
    esql->insert_id=       client_client_net_field_length_ll(&pos);

    esql->server_status= uint2korr(pos); pos+=2;
    esql->warning_count= uint2korr(pos); pos+=2;

    if (pos < esql->net.read_pos+length &&  client_net_field_length(&pos))
      esql->info=(char*) pos;
    return 0;
  }
  if (field_count == NULL_LENGTH)    /* LOAD DATA LOCAL INFILE */
  {
    client_set_error(esql, CR_MALFORMED_PACKET,client_sqlstate_get_unknown());

    return 1;
  }
  if (!(esql->server_status & SERVER_STATUS_AUTOCOMMIT))
    esql->server_status|= SERVER_STATUS_IN_TRANS;

  if (!(fields=client_cli_read_rows(esql,(ESQL_FIELD*)0, 7)))
    return(1);
  if (!(esql->fields= client_unpack_fields(fields, (uint32_t) field_count, 0)))
    return(1);
  esql->status= ESQL_STATUS_GET_RESULT;
  esql->field_count= (uint32_t) field_count;
  return(0);
}


/*
  Send the query and return so we can do something else.
  Needs to be followed by client_read_query_result() when we want to
  finish processing it.
*/

int32_t
client_send_query(ESQL *esql, const char* query, uint32_t length)
{
  return(simple_command(esql, COM_QUERY, (unsigned char*) query, length, 1));
}


int32_t
client_real_query(ESQL *esql, const char *query, uint32_t length)
{
  if (client_send_query(esql,query,length))
    return(1);
  return((int) (*esql->methods->read_query_result)(esql));
}


/**************************************************************************
  Alloc result struct for buffered results. All rows are read to buffer.
  client_data_seek may be used.
**************************************************************************/

ESQL_RES * client_store_result(ESQL *esql)
{
  ESQL_RES *result;

  if (!esql->fields)
    return(0);
  if (esql->status != ESQL_STATUS_GET_RESULT)
  {
    client_set_error(esql, CR_COMMANDS_OUT_OF_SYNC, client_sqlstate_get_unknown());
    return(0);
  }
  esql->status=ESQL_STATUS_READY;    /* server is ready */
  if (!(result=(ESQL_RES*) malloc((uint32_t) (sizeof(ESQL_RES)+
                sizeof(uint32_t) *
                esql->field_count))))
  {
    client_set_error(esql, CR_OUT_OF_MEMORY, client_sqlstate_get_unknown());
    return(0);
  }
  memset(result, 0,(sizeof(ESQL_RES)+ sizeof(uint32_t) *
                    esql->field_count));
  result->methods= esql->methods;
  result->eof= 1;        /* Marker for buffered */
  result->lengths= (uint32_t*) (result+1);
  if (!(result->data=
  (*esql->methods->read_rows)(esql,esql->fields,esql->field_count)))
  {
    free((unsigned char*) result);
    return(0);
  }
  esql->affected_rows= result->row_count= result->data->rows;
  result->data_cursor=  result->data->data;
  result->fields=  esql->fields;
  result->field_count=  esql->field_count;
  /* The rest of result members is zeroed in malloc */
  esql->fields=0;        /* fields is now in result */
  /* just in case this was mistakenly called after stmt_execute() */
  esql->unbuffered_fetch_owner= 0;
  return(result);        /* Data fetched */
}


/**************************************************************************
  Alloc struct for use with unbuffered reads. Data is fetched by demand
  when calling to client_fetch_row.
  ESQL_DATA_seek is a noop.

  No other queries may be specified with the same ESQL handle.
  There shouldn't be much processing per row because ESQL server shouldn't
  have to wait for the client (and will not wait more than 30 sec/packet).
**************************************************************************/

ESQL_RES *  client_cli_use_result(ESQL *esql)
{
  ESQL_RES *result;

  if (!esql->fields)
    return(0);
  if (esql->status != ESQL_STATUS_GET_RESULT)
  {
    client_set_error(esql, CR_COMMANDS_OUT_OF_SYNC,  client_sqlstate_get_unknown());
    return(0);
  }
  if (!(result=(ESQL_RES*) malloc(sizeof(*result)+
                                     sizeof(uint32_t)*esql->field_count)))
    return(0);
  memset(result, 0, sizeof(*result)+ sizeof(uint32_t)*esql->field_count);
  result->lengths=(uint32_t*) (result+1);
  result->methods= esql->methods;
  if (!(result->row=(ESQL_ROW)
        malloc(sizeof(result->row[0])*(esql->field_count+1))))
  {          /* Ptrs: to one row */
    free((unsigned char*) result);
    return(0);
  }
  result->fields=  esql->fields;
  result->field_count=  esql->field_count;
  result->current_field=0;
  result->handle=  esql;
  result->current_row=  0;
  esql->fields=0;      /* fields is now in result */
  esql->status=ESQL_STATUS_USE_RESULT;
  esql->unbuffered_fetch_owner= &result->unbuffered_fetch_cancelled;
  return(result);      /* Data is read to be fetched */
}



/**
   Set the internal error message to ESQL handler

   @param esql connection handle (client side)
   @param errcode  CR_ error code, passed to ER macro to get
   error text
   @parma sqlstate SQL standard sqlstate
*/

void client_set_error(ESQL *esql, int errcode, const char *sqlstate)
{
  NET *net;
  assert(esql != 0);

  if (esql)
  {
    net= &esql->net;
    net->last_errno= errcode;
    strcpy(net->last_error, CLIENT_ER(errcode));
    strcpy(net->sqlstate, sqlstate);
  }
  else
  {
    esql_server_last_errno= errcode;
    strcpy(esql_server_last_error, CLIENT_ER(errcode));
  }
  return;
}


unsigned int client_errno(const ESQL *esql)
{
  return esql ? esql->net.last_errno : esql_server_last_errno;
}


const char * client_error(const ESQL *esql)
{
  return esql ? _(esql->net.last_error) : _(esql_server_last_error);
}

/**
   Set an error message on the client.

   @param esql       connection handle
   @param errcode   CR_* errcode, for client errors
   @param sqlstate  SQL standard sql state, client_sqlstate_get_unknown() for the
   majority of client errors.
   @param format    error message template, in sprintf format
   @param ...       variable number of arguments
*/

void client_set_extended_error(ESQL *esql, int errcode,
                                const char *sqlstate,
                                const char *format, ...)
{
  NET *net;
  va_list args;
  assert(esql != 0);

  net= &esql->net;
  net->last_errno= errcode;
  va_start(args, format);
  vsnprintf(net->last_error, sizeof(net->last_error)-1,
            format, args);
  va_end(args);
  strcpy(net->sqlstate, sqlstate);

  return;
}



/*
  Flush result set sent from server
*/

void client_cli_flush_use_result(ESQL *esql)
{
  /* Clear the current execution status */
  for (;;)
  {
    uint32_t pkt_len;
    if ((pkt_len=client_cli_safe_read(esql)) == packet_error)
      break;
    if (pkt_len <= 8 && esql->net.read_pos[0] == COLLAPSAR_PROTOCOL_NO_MORE_DATA)
    {
      char *pos= (char*) esql->net.read_pos + 1;
      esql->warning_count=uint2korr(pos); pos+=2;
      esql->server_status=uint2korr(pos); pos+=2;

      break;                            /* End of data */
    }
  }
  return;
}

/**************************************************************************
  Get column lengths of the current row
  If one uses client_use_result, res->lengths contains the length information,
  else the lengths are calculated from the offset between pointers.
**************************************************************************/

void client_cli_fetch_lengths(uint32_t *to, ESQL_ROW column, uint32_t field_count)
{
  uint32_t *prev_length;
  char *start=0;
  ESQL_ROW end;

  prev_length=0;        /* Keep gcc happy */
  for (end=column + field_count + 1 ; column != end ; column++, to++)
  {
    if (!*column)
    {
      *to= 0;          /* Null */
      continue;
    }
    if (start)          /* Found end of prev string */
      *prev_length= (uint32_t) (*column-start-1);
    start= *column;
    prev_length= to;
  }
}

